//https://banu.com/blog/2/how-to-use-epoll-a-complete-example-in-c/epoll-example.c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netdb.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/epoll.h>
#include <errno.h>

#define MAXEVENTS 64

#if 1
// 下列代码段可实现主动访问已经连接的TCP Client
int tcp_client_fds[MAXEVENTS];
int tcp_client_cnt;

int * get_tcp_clients(void)
{
    return tcp_client_fds;
}

static int add_tcp_client(int fd)
{
    int i;
    int *p_tcp_client = tcp_client_fds;
    int *p_tcp_client_cnt = &tcp_client_cnt;

    if(fd < 0)
    {
        return -1;
    }

    if(p_tcp_client[0] >= MAXEVENTS)
    {
        return -1;
    }

    for(i =0; i < MAXEVENTS; i++)
    {
        if(p_tcp_client[i] <= 0)
        {
            p_tcp_client[i] = fd;
            p_tcp_client_cnt[0] ++;
            printf("TCP CLIENT [%d] connected, now we have [%d] client(s)\n", fd, p_tcp_client_cnt[0]);
            return 0;
        }
    }
    return -1;
}

static int del_tcp_client(int fd)
{
    int i;
    int *p_tcp_client = tcp_client_fds;
    int *p_tcp_client_cnt = &tcp_client_cnt;

    for(i =0; i < MAXEVENTS; i++)
    {
        if(p_tcp_client[i] == fd)
        {
            p_tcp_client[i] = -1;
            p_tcp_client_cnt[0] --;
            printf("TCP CLIENT [%d] disconnected, now we have [%d] client(s)\n", fd, p_tcp_client_cnt[0]);
            return 0;
        }
    }
    return -1;
}
#endif

static int make_socket_non_blocking (int sfd)
{
    int flags, s;

    flags = fcntl (sfd, F_GETFL, 0);
    if (flags == -1)
    {
        perror ("fcntl");
        return -1;
    }

    flags |= O_NONBLOCK;
    s = fcntl (sfd, F_SETFL, flags);
    if (s == -1)
    {
        perror ("fcntl");
        return -1;
    }

    return 0;
}

int creat_tcp_server(int *fd, int port, int backlog)
{
    int tcp_listenfd, one = 1;
    int ret = 0;

    struct sockaddr_in address;

    bzero(&address, sizeof(address));
    address.sin_family = AF_INET;
    address.sin_addr.s_addr = htonl(INADDR_ANY);;
    address.sin_port = htons(port);

    tcp_listenfd = *fd;

    //msleep(100);
    if(tcp_listenfd != -1)
    {
        close(tcp_listenfd);
    }
    //创建tcp socket ， 并将其绑定到端口port上
    tcp_listenfd = socket(PF_INET, SOCK_STREAM, 0);

    if (setsockopt(tcp_listenfd, SOL_SOCKET, SO_REUSEADDR, &one, sizeof(one)) < 0) {
        close(tcp_listenfd);
        perror("reuse socket");
        return -1;
    }

    ret = bind(tcp_listenfd, (struct sockaddr*)&address, sizeof(address));
    if(ret != 0)
    {
        close(tcp_listenfd);
        perror("bind socket");
        return -1;
    }

    ret = listen(tcp_listenfd, backlog);
    if(ret != 0)
    {
        close(tcp_listenfd);
        perror("listen socket");
        return -1;
    }

    *fd = tcp_listenfd;
    return ret;
}

static void addfd_to_epoll(int epfd, int fd)
{
    struct epoll_event event;
    event.data.fd = fd;
    event.events = EPOLLIN | EPOLLET; //边沿触发要求套接字为非阻塞模式；水平触发可以是阻塞或非阻塞模式
    epoll_ctl(epfd, EPOLL_CTL_ADD, fd, &event);
    make_socket_non_blocking(fd);
}

int main (int argc, char *argv[])
{
    int sfd = -1, s;
    int efd, ret;
    int n, i;

    int done = 0;
    ssize_t count;
    char buf[512];

    struct sockaddr in_addr;
    socklen_t in_len;
    int infd;
    char hbuf[NI_MAXHOST], sbuf[NI_MAXSERV];

    struct epoll_event event;
    struct epoll_event *events;

    if (argc != 2)
    {
        fprintf (stderr, "Usage: %s [port]\n", argv[0]);
        exit (EXIT_FAILURE);
    }

    tcp_client_cnt = 0;
    for(i = 0; i < MAXEVENTS; i ++)
    {
        tcp_client_fds[i] = -1;;
    }

    ret = creat_tcp_server (&sfd, atoi(argv[1]), 20);
    if (ret == -1)
    {
        abort ();
    }

    efd = epoll_create1 (0);
    if (efd == -1)
    {
        perror ("epoll_create");
        abort ();
    }

    addfd_to_epoll(efd, sfd);

    /* Buffer where events are returned */
    events = (struct epoll_event *) calloc (MAXEVENTS, sizeof(event));

    /* The event loop */
    while (1)
    {
        n = epoll_wait (efd, events, MAXEVENTS, -1);
        for (i = 0; i < n; i++)
        {
            if ((events[i].events & EPOLLERR) ||
                    (events[i].events & EPOLLHUP) ||
                    (!(events[i].events & EPOLLIN)))
            {
                /* An error has occured on this fd, or the socket is not
                   ready for reading (why were we notified then?) */
                fprintf (stderr, "epoll error\n");
                close (events[i].data.fd);
                continue;
            }

            else if (sfd == events[i].data.fd)
            {
                /* We have a notification on the listening socket, which
                   means one or more incoming connections. */
                while (1)
                {
                    in_len = sizeof(in_addr);
                    infd = accept (sfd, &in_addr, &in_len);
                    if (infd == -1)
                    {
                        if ((errno == EAGAIN) ||
                                (errno == EWOULDBLOCK))
                        {
                            /* We have processed all incoming
                               connections. */
                            break;
                        }
                        else
                        {
                            perror ("accept");
                            break;
                        }
                    }

                    //s = getnameinfo (&in_addr, in_len,
                    //        hbuf, sizeof hbuf,
                    //        sbuf, sizeof sbuf,
                    //        NI_NUMERICHOST | NI_NUMERICSERV);
                    //if (s == 0)
                    //{
                    //    printf("Accepted connection on descriptor %d "
                    //            "(host=%s, port=%s)\n", infd, hbuf, sbuf);
                    //}

                    /* Make the incoming socket non-blocking and add it to the
                       list of fds to monitor. */
                    addfd_to_epoll(efd, infd);

                    add_tcp_client(infd);
                }
                continue;
            }
            else
            {
                /* We have data on the fd waiting to be read. Read and
                   display it. We must read whatever data is available
                   completely, as we are running in edge-triggered mode
                   and won't get a notification again for the same
                   data. */
                done = 0;

                while (1)
                {
                    count = read (events[i].data.fd, buf, sizeof buf);
                    if (count == -1)
                    {
                        /* If errno == EAGAIN, that means we have read all
                           data. So go back to the main loop. */
                        if (errno != EAGAIN)
                        {
                            perror ("read");
                            done = 1;
                        }
                        break;
                    }
                    else if (count == 0)
                    {
                        /* End of file. The remote has closed the
                           connection. */
                        done = 1;
                        break;
                    }

                    /* Write the buffer to standard output */
                    s = write (1, buf, count);
                    if (s == -1)
                    {
                        perror ("write");
                        abort ();
                    }
                }

                if (done)
                {
                    printf ("Closed connection on descriptor %d\n",
                            events[i].data.fd);

                    /* Closing the descriptor will make epoll remove it
                       from the set of descriptors which are monitored. */
                    close (events[i].data.fd);
                    del_tcp_client(events[i].data.fd);
                }
            }
        }
    }

    free (events);

    close (efd);
    close (sfd);

    return EXIT_SUCCESS;
}
